package leetcode;

import java.util.Comparator;
import java.util.Deque;
import java.util.LinkedList;
import java.util.PriorityQueue;

/**
 * @program: datastructureandalogorithm
 * @description:
 * @author: hmx
 * @create: 2021-10-23 17:40
 **/
public class LeetCode239 {

    /**
     * 使用了最普通的解法,时间复杂度为O(kn),无法通过此题，毕竟是个困难题。
     * @param nums
     * @param k
     * @return
     */
    public int[] maxSlidingWindow2(int[] nums, int k) {
        //数组长度
        int size = nums.length;
        //定义最大值数组
        int[] max = new int[size - k + 1];
        //指向左边界
        int i = 0;
        //指向右边界
        int j = k - 1;
        while (j < size) {
            max[i] = nums[i];
            for (int h = 1; h < k; h++) {
                max[i] = Math.max(max[i], nums[i + h]);
            }
            i++;
            j++;
        }
        return max;
    }

    /**
     * 使用了优先队列Priority
     * @param nums
     * @param k
     * @return
     */
    public int[] maxSlidingWindow3(int[] nums, int k) {
        //数组的长度
        int size = nums.length;
        //声明一个优先队列，定义比较器，使该队列为大根堆(根节点最大)(即除了叶子结点外,每个节点都大于他的两个子节点)
        //堆是完全二叉树,其可以用数组来表示。
        //Comparator接口中重写方法时，我的理解是返回值时后面的减前面的就是降序，否则是升序。
        PriorityQueue<int[]> pq = new PriorityQueue<int[]>(new Comparator<int[]>() {
            @Override
            public int compare(int[] a, int[] b) {
                return a[0] != b[0] ? b[0] - a[0] : b[1] - a[1];
            }
        });
        //max数组
        int[] max = new int[size - k + 1];
        //用来指向遍历的位置
        int i = 0;
        for (; i < k; i++) {
            //添加一个数组元素(数组0索引表示元素值,1索引表示元素在数组中的下标)(1索引用来判断当前根节点是否超出的滑动窗口的范围)
            pq.offer(new int[]{nums[i], i});
        }
        //得到第一个滑动窗口内元素的最大值
        max[0] = pq.peek()[0];
        for (; i < size; i++) {
            pq.offer(new int[]{nums[i], i});
            while (pq.peek()[1] < i - k + 1) {
                //当根节点(存储最大值)的元素超出滑动窗口的返回，就删掉此节点
                pq.poll();
            }
            //得到当前滑动窗口的最大值
            max[i - k + 1] = pq.peek()[0];
        }
        return max;
    }

    /**
     * 使用了双端队列LinkedList作为单调队列
     * @param nums
     * @param k
     * @return
     */
    public int[] maxSlidingWindow(int[] nums, int k) {
        //数组的长度
        int size = nums.length;
        //使用双端队列来维护一个单调递增的元素索引(索引递减,对应的元素值递增)
        Deque<Integer> deque = new LinkedList<>();
        //存储滑动窗口最大值
        int[] max = new int[size - k + 1];
        //当前遍历元素所在索引
        int i = 0;
        for (; i < k; i++) {
            //peekLast(): 查看队列最后一个元素
            while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
                //poolLast(): 移除队列的最后一个元素
                //当队列不为空,且左边元素小于右边时，此元素没用了，直接移除
                deque.pollLast();
            }
            //offerLast()&offer():将元素添加到队列尾部
            //将当前元素的索引放到队列尾部中
            deque.offerLast(i);
        }
        //此时是第一个滑动窗口,最头部元素所指向的索引值是当前窗口中(从第二个开始就不一定了)最大的
        max[0] = nums[deque.peekFirst()];

        for (; i < size; i++) {
            while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
                deque.pollLast();
            }
            deque.offerLast(i);
            //如果当前的头部元素已经不在当前窗口，移除该头部元素
            if (deque.peekFirst() < i - k + 1) {
                deque.pollFirst();
            }
            max[i - k + 1] = nums[deque.peekFirst()];
        }
        return max;
    }

}
